/* eslint-disable react/prop-types */
import React from 'react'
import cssStyle from './index.module.css'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import { Form } from '../index'

function deepCompare() {
    var i, l, leftChain, rightChain
    function compare2Objects(x, y) {
        var p
        if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
            return true
        }
        if (x === y) {
            return true
        }
        if ((typeof x === 'function' && typeof y === 'function') ||
            (x instanceof Date && y instanceof Date) ||
            (x instanceof RegExp && y instanceof RegExp) ||
            (x instanceof String && y instanceof String) ||
            (x instanceof Number && y instanceof Number)) {
            return x.toString() === y.toString()
        }
        if (!(x instanceof Object && y instanceof Object)) {
            return false
        }
        // eslint-disable-next-line no-prototype-builtins
        if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
            return false
        }
        if (x.constructor !== y.constructor) {
            return false
        }
        if (x.prototype !== y.prototype) {
            return false
        }
        if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
            return false
        }
        for (p in y) {
            // eslint-disable-next-line no-prototype-builtins
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
                return false
            } else if (typeof y[p] !== typeof x[p]) {
                return false
            }
        }
        for (p in x) {
            // eslint-disable-next-line no-prototype-builtins
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
                return false
            } else if (typeof y[p] !== typeof x[p]) {
                return false
            }
            switch (typeof(x[p])) {
                case 'object':
                case 'function':
                    leftChain.push(x)
                    rightChain.push(y)
                    if (!compare2Objects(x[p], y[p])) {
                        return false
                    }
                    leftChain.pop()
                    rightChain.pop()
                    break
                default:
                    if (x[p] !== y[p]) {
                        return false
                    }
                    break
            }
        }
        return true
    }
    if (arguments.length < 1) {
        return true
    }
    for (i = 1, l = arguments.length; i < l; i++) {
        leftChain = []
        rightChain = []
        if (!compare2Objects(arguments[0], arguments[i])) {
            return false
        }
    }
    return true
}

class Column extends React.PureComponent{
    render(){
        // eslint-disable-next-line no-unused-vars
        let { renderType, prop, align, width, render, lazy, type, checked, selectAll, label, ...rest } = this.props
        let Com = renderType
        return(
            <Com {...rest}>
                {
                    Com === 'th'
                    ?
                    (
                        type && type === 'selection'
                        ?
                        <Form.checkbox checked={ checked } onChange={ selectAll } />
                        :
                        label
                    )
                    :
                    rest.children
                }
            </Com>
        )
    }
}

Column.propTypes = {
    renderType: PropTypes.string
}

class Table extends React.PureComponent{
    constructor(props){
        super(props)
        this.state = {
            itemWidth: 0,
            tableWidth: 0,
            multipleSelectList: [],
            checked: false
        }
        this.tableRef = React.createRef()
        this.tbodyRef = React.createRef()
    }
    static getDerivedStateFromProps(nextProps, prevState){
        if(nextProps.multipleSelectList){
            prevState.multipleSelectList = nextProps.multipleSelectList
            let unExistPosition = []
            if(nextProps.data.length){
                for(let i = 0; i < prevState.multipleSelectList.length; i++){
                    let exist = false  //当前已选项存在于nextProps.data中的标志
                    for(let j = 0; j < nextProps.data.length; j++){
                        if(deepCompare(prevState.multipleSelectList[i], nextProps.data[j])){
                            exist = true
                            break
                        }
                    }
                    if(!exist){  //遍历当前已选项中不存在于nextProps.data中的项的位置信息
                        unExistPosition.push(i)
                    }
                }
                if(unExistPosition.length){
                    prevState.checked = false  //存在已选不存在，则取消全选
                    let filterArr = prevState.multipleSelectList.filter((item, index) => {  //找出已选项存在于nextProps.data中的项
                        return unExistPosition.indexOf(index) === -1
                    })
                    prevState.multipleSelectList.splice(0)  //清空当前已选项
                    if(filterArr.length !== 0){  //重新追加存在于nextProps.data中的已选项
                        prevState.multipleSelectList.push(...filterArr)
                    }
                }else{  //当前以选项都存在于nextProps.data中
                    if(prevState.multipleSelectList.length === nextProps.data.length){
                        prevState.checked = true  //且长度相等则全选
                    }else{
                        prevState.checked = false
                    }
                }
            }else{
                prevState.multipleSelectList.splice(0)
                prevState.checked = false
            }
        }
        return prevState
    }
    componentDidMount(){
        setTimeout(() => {
            let tableWidth = 0
            let noWidthColumnNum = 0
            React.Children.forEach(this.props.children, item => {
                if(item){
                    if(item.props.width){
                        tableWidth += item.props.width ? parseFloat(item.props.width) : 0
                    }else{
                        noWidthColumnNum++
                    }
                }
            })
            let containerWidth = this.tableRef.current.offsetWidth
            if(noWidthColumnNum > 0){
                if(tableWidth >= containerWidth){
                    this.setState({
                        itemWidth: 80,
                        // tableWidth: tableWidth + noWidthColumnNum*80,
                        tableWidth: containerWidth
                    })
                }else{
                    let overWidth = containerWidth - tableWidth
                    this.setState({
                        itemWidth: overWidth/noWidthColumnNum,
                        tableWidth: containerWidth
                    })
                }
            }else{
                if(tableWidth >= containerWidth){
                    this.setState({
                        itemWidth: 0,
                        // tableWidth: tableWidth,
                        tableWidth: containerWidth
                    })
                }else{
                    let overWidth = containerWidth - tableWidth
                    this.setState({
                        itemWidth: overWidth/noWidthColumnNum,
                        tableWidth: containerWidth
                    })
                }
            }
        })
    }
    selectionChange = item => {
        let multipleSelectList = [...this.state.multipleSelectList]
        let index
        for(let i = 0; i < multipleSelectList.length; i++){
            if(deepCompare(multipleSelectList[i], item)){
                index = i
                break
            }
        }
        if(index !== undefined){
            multipleSelectList.splice(index, 1)
        }else{
            multipleSelectList.push(item)
        }
        this.props.selectionChange(multipleSelectList)
    }
    selectAll = () => {
        if(this.state.multipleSelectList.length === this.props.data.length){
            this.props.selectionChange([])
        }else{
            this.props.selectionChange([...this.props.data])
        }
    }
    expand = (f, item, index, lazy) => {
        let children = Array.from(this.tbodyRef.current.children)
        let currentElement = children.filter(item => {
            return item.dataset.index == index
        })[0]
        if(currentElement.dataset.expand){
            if(currentElement.dataset.lazy){
                return
            }
            currentElement.removeAttribute("data-expand")
            this.tbodyRef.current.removeChild(currentElement.nextElementSibling)
        }else{
            currentElement.setAttribute("data-expand", true)
            let tr = document.createElement('tr')
            let td = document.createElement('td')
            td.setAttribute("colspan", this.props.children.length)
            if(lazy){
                currentElement.setAttribute("data-lazy", true)
                f(item).then(res => {
                    ReactDOM.render(
                        res,
                        td
                    )
                    tr.appendChild(td)
                    this.tbodyRef.current.insertBefore(tr, currentElement.nextElementSibling)
                }).finally(() => {
                    currentElement.removeAttribute("data-lazy")
                })
            }else{
                currentElement.setAttribute("data-expand", true)
                ReactDOM.render(
                    f(item),
                    td
                )
                tr.appendChild(td)
                this.tbodyRef.current.insertBefore(tr, currentElement.nextElementSibling)
            }
        }
    }
    clearSelection = () => {
        this.setState(state => {
            state.multipleSelectList.splice(0)
            return state
        })
    }
    clearExpand(){
        let children = Array.from(this.tbodyRef.current.children)
        if(children.length){
            let needRemoveChildren = []
            children.forEach(item => {
                if(item.dataset.index === undefined){
                    needRemoveChildren.push(item)
                }
                if(item.dataset.expand !== undefined){
                    item.removeAttribute("data-expand")
                }
                if(item.dataset.lazy !== undefined){
                    item.removeAttribute("data-lazy")
                }
            })
            if(needRemoveChildren.length){
                needRemoveChildren.forEach(item => {
                    this.tbodyRef.current.removeChild(item)
                })
            }
        }
    }
    render(){
        return(
            <div ref={ this.tableRef } className={ cssStyle.table } style={ this.props.style || {} }>
                <table style={{ width: '100%', minWidth: '100%', textAlign: this.props.align ? this.props.align : 'left' }}>
                    <colgroup>
                        {
                            React.Children.map(this.props.children, (item, index) => {
                                if(item){
                                    return(
                                        <col key={ index } width={ item.props.width ? item.props.width : this.state.itemWidth } />
                                    )
                                }
                            })
                        }
                    </colgroup>
                    <thead>
                        <tr>
                            {
                                React.Children.map(this.props.children, item => {
                                    if(item){
                                        if(item.props.type && item.props.type === 'selection'){
                                            return React.cloneElement(item, {
                                                renderType: 'th',
                                                style: {
                                                    textAlign: item.props.align ? item.props.align : (this.props.align ? this.props.align : 'left')
                                                },
                                                checked: this.state.checked,
                                                selectAll: this.selectAll
                                            })
                                        }else{
                                            return React.cloneElement(item, {
                                                renderType: 'th',
                                                style: {
                                                    textAlign: item.props.align ? item.props.align : (this.props.align ? this.props.align : 'left')
                                                }
                                            })
                                        }
                                    }
                                })
                            }
                        </tr>
                    </thead>
                    <tbody className={ cssStyle.tbody } ref={ this.tbodyRef }>
                        {
                            this.props.data.length
                            ?
                            this.props.data.map((item, index) => {
                                let checked = false
                                for(let i = 0; i < this.state.multipleSelectList.length; i++){
                                    if(deepCompare(this.state.multipleSelectList[i], item)){
                                        checked = true
                                        break
                                    }
                                }
                                return(
                                    <tr
                                        key={index}
                                        data-index={index}
                                    >
                                        {
                                            React.Children.map(this.props.children, _item => {
                                                if(_item){
                                                    let { type, align, prop, lazy, render } = _item.props
                                                    let Component = (
                                                        type === 'selection'
                                                        ?
                                                        <Form.checkbox checked={ checked } onChange={ () => { this.selectionChange(item) } } />
                                                        :
                                                        (
                                                            type === 'expand'
                                                            ?
                                                            <span className={ cssStyle.expand } onClick={ () => { this.expand(render, item, index, lazy) } }>&gt;</span>
                                                            :
                                                            (
                                                                prop
                                                                ?
                                                                item[prop]
                                                                :
                                                                (
                                                                    render
                                                                    ?
                                                                    <div style={{ display: 'inline-block'}}>
                                                                        {
                                                                            render(item, index)
                                                                        }
                                                                    </div>
                                                                    :
                                                                    ''
                                                                )
                                                            )
                                                        )
                                                    )
                                                    return React.cloneElement(_item, {
                                                        renderType: 'td',
                                                        style: {
                                                            textAlign: align ? align : (this.props.align ? this.props.align : 'left'),
                                                            wordBreak: 'break-all'
                                                        }
                                                    }, Component)
                                                }
                                            })
                                        }
                                    </tr>
                                )
                            })
                            :
                            <tr data-index={ 0 }>
                                <td colSpan={ this.props.children.length }>
                                    <div className={ cssStyle.nodata }>暂无数据</div>
                                </td>
                            </tr>
                        }
                    </tbody>
                </table>
                {
                    this.props.loading
                    ?
                    <div className={ cssStyle.loadingMask }>
                        <div className={ cssStyle.loadingSpinner }>
                            <svg viewBox="25 25 50 50" className={ cssStyle.circular }>
                                <circle cx="50" cy="50" r="20" fill="none" className={ cssStyle.path }></circle>
                            </svg>
                        </div>
                    </div>
                    :
                    null
                }
            </div>
        )
    }
}

Table.propTypes = {
    children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
    align: PropTypes.string,
    style: PropTypes.object,
    data: PropTypes.array
}

export default {
    table: Table,
    column: Column
}